package org.xydpeng.framework.l2cache.biz;

import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import jakarta.annotation.Resource;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.data.redis.core.RedisTemplate;
import org.xydpeng.framework.l2cache.enums.CacheOperation;
import org.xydpeng.framework.l2cache.message.CacheMessage;

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @author xydpeng
 * @version v1.0
 * @date: 2024/6/14
 * @description:
 */
public abstract class AbstractCacheService<K, R> implements CacheService<K, R> {

    public static final ScheduledThreadPoolExecutor scheduledThreadPool = new ScheduledThreadPoolExecutor(4);

    protected String cacheName;

    @Resource(name = "stringKeyRedisTemplate")
    protected RedisTemplate<String, R> redisTemplate;

    protected LoadingCache cache;
    protected Semaphore semaphore;


    protected void init() {
        this.cache = Caffeine.newBuilder()
                .initialCapacity(16)
                .maximumSize(1024)
                .expireAfterWrite(600L, TimeUnit.SECONDS)
                .build(new GenericCacheLoader());

        this.semaphore = new Semaphore(4);
    }

    class GenericCacheLoader implements CacheLoader<K, R> {
        @Override
        public @Nullable R load(K key) throws Exception {
            Object value = redisTemplate.opsForValue().get(buildKey(key));
            if (value != null) {
                return (R) value;
            }
            semaphore.acquire();
            try {
                R r = loadData(key);
                redisTemplate.opsForValue().set(buildKey(key), r);
            } catch (Exception e) {

            } finally {
                semaphore.release();
            }

            return null;
        }
    }

    @Override
    public R get(K key) {
        return (R) cache.get(key);
    }

    @Override
    public void evict(K key) {
        redisTemplate.delete(buildKey(key));

        redisTemplate.convertAndSend("topic", new CacheMessage(cacheName, key, CacheOperation.EVICT));

        cache.invalidate(key);
    }

    @Override
    public R reload(K key) {
        return (R) cache.get(key);
    }

    @Override
    public void invalidateL1Cache(K key) {
        cache.invalidate(key);
    }

    public abstract R loadData(K key);

    protected abstract String buildKey(K key);

}
